package org.hackystat.project.explorer;

import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import java.util.logging.Logger;
import javax.xml.datatype.XMLGregorianCalendar;
import org.hackystat.dailyprojectdata.client.DailyProjectDataClient;
import org.hackystat.dailyprojectdata.client.DailyProjectDataClientException;
import org.hackystat.dailyprojectdata.resource.filemetric.jaxb.FileMetricDailyProjectData;
import org.hackystat.project.DailyEffort;
import org.hackystat.project.ProjectException;
import org.hackystat.project.ProjectSummary;
import org.hackystat.projecttrajectory.ProjectTrajectoryProperties;
import org.hackystat.sensorbase.client.SensorBaseClient;
import org.hackystat.sensorbase.client.SensorBaseClientException;
import org.hackystat.sensorbase.resource.projects.jaxb.Members;
import org.hackystat.sensorbase.resource.projects.jaxb.Project;
import org.hackystat.sensorbase.resource.projects.jaxb.ProjectIndex;
import org.hackystat.sensorbase.resource.projects.jaxb.ProjectRef;
import org.hackystat.sensorbase.resource.projects.jaxb.UriPatterns;
import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorData;
import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDataIndex;
import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorDataRef;
import org.hackystat.sensorbase.resource.users.jaxb.UserIndex;
import org.hackystat.sensorbase.resource.users.jaxb.UserRef;
import org.hackystat.utilities.logger.HackystatLogger;
import org.hackystat.utilities.time.period.Day;
import org.hackystat.utilities.tstamp.Tstamp;

/**
 * Provides a way of examining Hackystat Projects.
 * 
 * @author Cam Moore
 * 
 */
public class ProjectExplorer {

  private SensorBaseClient sbClient;
  private DailyProjectDataClient dpdClient;
  private ProjectTrajectoryProperties properties;
  private Logger logger;
  private static String totalLines = "TotalLines";

  // private Level debugLevel = Level.FINEST;

  /**
   * Constructor for ProjectExplorer.
   * 
   * @throws ProjectException
   *             if can't authenticate the sensorbase and dailyprojectdata.
   */
  public ProjectExplorer() throws ProjectException {
    logger = HackystatLogger.getLogger("org.hackystat.project.explorer.ProjectExplorer");
    properties = ProjectTrajectoryProperties.getInstance();
    sbClient = new SensorBaseClient(properties.getSensorbase(), properties.getEmail(), properties
        .getPassword());
    try {
      sbClient.authenticate();
    }
    catch (SensorBaseClientException e) {
      logger.warning("Could not authenticate Sensorbase " + properties.getSensorbase());
      throw new ProjectException("Could not authenticate Sensorbase " + properties.getSensorbase(),
          e);
    }
    dpdClient = new DailyProjectDataClient(properties.getDailyProjectData(), properties.getEmail(),
        properties.getPassword());
    try {
      dpdClient.authenticate();
    }
    catch (DailyProjectDataClientException e) {
      logger.warning("Could not authenticate DailyProjectData " + properties.getDailyProjectData());
      throw new ProjectException("Could not authenticate DailyProjectData "
          + properties.getDailyProjectData(), e);
    }
  }

  /**
   * Creates a new Project in the Sensorbase with the user's email.
   * 
   * @param projectName
   *            a String, the name of the Project.
   * @param description
   *            a String, the project description.
   * @param uris
   *            a List<String>, the URIs for the project.
   * @param start
   *            an XMLGregorianCalendar, the start date.
   * @param end
   *            an XMLGregorianCalendar, the end date.
   * @throws SensorBaseClientException
   *             if there is a problem adding the Project.
   */
  public void createProject(String projectName, String description, List<String> uris,
      XMLGregorianCalendar start, XMLGregorianCalendar end) throws SensorBaseClientException {
    // First, create a sample Project.
    String owner = properties.getEmail();
    Project project = new Project();
    project.setOwner(owner);
    project.setName(projectName);
    project.setDescription(description);
    project.setStartTime(start);
    project.setEndTime(end);
    UriPatterns uriPatterns = new UriPatterns();
    for (int i = 0; i < uris.size(); i++) {
      uriPatterns.getUriPattern().add(uris.get(i));
    }
    project.setUriPatterns(uriPatterns);
    Members members = new Members();
    project.setMembers(members);
    sbClient.putProject(project);
  }

  /**
   * @return A List of projects defined for the user.
   */
  public List<Project> getUserProjects() {
    return getUserProjects(properties.getEmail());
  }

  /**
   * @param email
   *            a String, the user's email address.
   * @return A List of Projects defined for the given user.
   */
  public List<Project> getUserProjects(String email) {
    ArrayList<Project> list = new ArrayList<Project>();
    try {
      ProjectIndex index = sbClient.getProjectIndex(email);
      for (ProjectRef ref : index.getProjectRef()) {
        list.add(sbClient.getProject(ref));
      }
    }
    catch (SensorBaseClientException e) {
      logger.warning(e.getMessage());
    }
    return list;
  }

  /**
   * @return A List of all the Projects defined in the Sensorbase.
   */
  public List<Project> getAllProjects() {
    ArrayList<Project> list = new ArrayList<Project>();
    try {
      UserIndex users = sbClient.getUserIndex();
      for (UserRef userRef : users.getUserRef()) {
        ProjectIndex projIndex = sbClient.getProjectIndex(userRef.getEmail());
        for (ProjectRef ref : projIndex.getProjectRef()) {
          list.add(sbClient.getProject(ref));
        }
      }
    }
    catch (SensorBaseClientException e) {
      logger.warning(e.getMessage());
    }
    return list;

  }

  /**
   * Returns a short String description of the given project.
   * 
   * @param project
   *            a Project.
   * @return A short String description of the project.
   */
  public String toShortDescription(Project project) {
    StringBuffer buff = new StringBuffer();
    buff.append('[');
    buff.append(project.getName());
    buff.append(" - ");
    buff.append(project.getDescription());
    buff.append(". ");
    buff.append(project.getOwner());
    buff.append(']');
    return buff.toString();
  }

  /**
   * Returns a detailed String description of the given project.
   * 
   * @param project
   *            a Project.
   * @return A detailed String description of the givne project.
   */
  public String toDetailedDescription(Project project) {
    StringBuffer buff = new StringBuffer(64);
    buff.append('[');
    buff.append(project.getName());
    buff.append(" - ");
    buff.append(project.getDescription());
    buff.append(". ");
    buff.append(project.getOwner());
    List<String> uriPatterns = project.getUriPatterns().getUriPattern();
    if (!uriPatterns.isEmpty()) {
      buff.append("\n URI: (");
      for (int i = 0; i < uriPatterns.size(); i++) {
        buff.append(uriPatterns.get(i));
        buff.append(", ");
      }
      buff.delete(buff.length() - 2, buff.length());
      buff.append(')');
    }
    // if (project.getMembers() != null) {
    List<String> members = project.getMembers().getMember();
    if (!members.isEmpty()) {
      buff.append("\n Members: {");
      for (int i = 0; i < members.size(); i++) {
        buff.append(members.get(i));
        buff.append(", ");
      }
      buff.delete(buff.length() - 2, buff.length());
      buff.append('}');
    }
    // }
    buff.append("\n Start date: ");
    buff.append(project.getStartTime().toString());
    buff.append("\n End date: ");
    buff.append(project.getEndTime().toString());
    buff.append(']');
    return buff.toString();

  }

  /**
   * Gets the current summary of size for the given project.
   * 
   * @param user
   *            a String the user.
   * @param projectName
   *            a String the project's name.
   * @param date
   *            an XMLGregorianCalendar the date for the summary.
   * @return A ProjectSummary.
   */
  public ProjectSummary getProjectSummary(String user, String projectName, 
      XMLGregorianCalendar date) {
    ProjectSummary summary = new ProjectSummary(date, projectName, 0, 0, 0);
    fillSummary(user, projectName, date, summary);
    return summary;
  }

  /**
   * Returns today's project summary for the given project and user.
   * 
   * @param user
   *            a String, the user's email.
   * @param projectName
   *            a String the project's name.
   * @return Today's ProjectSummary for the given user and project name.
   */
  public ProjectSummary getTodaysSummary(String user, String projectName) {
    return getProjectSummary(user, projectName, Tstamp.makeTimestamp(Day.getInstance()));
  }

  /**
   * Fills the given ProjectSummary.
   * 
   * @param user
   *            a String, the user's email.
   * @param projectName
   *            a String, the project name.
   * @param date
   *            a XMLGregorianCalendar the date.
   * @param summary
   *            a ProjectSummary.
   */
  private void fillSummary(String user, String projectName, XMLGregorianCalendar date,
      ProjectSummary summary) {
    try {
      // find first day of data
      XMLGregorianCalendar dataStartTime = startDate(user, projectName);
      // get total lines
      FileMetricDailyProjectData fileData = dpdClient.getFileMetric(user, projectName, date,
          totalLines);
      if (fileData.getTotal() > 0) {
        summary.setTotalLines(fileData.getTotal());
      }
      else {
        // look backwards by day unil we find a size or we get to the start day
        // of the project.
        // Project proj = sbClient.getProject(user, projectName);
        XMLGregorianCalendar currentTime = date;
        long size = 0;
        while (size == 0 && Tstamp.greaterThan(currentTime, dataStartTime)) {
          // decrement currentTime by one day
          currentTime = Tstamp.incrementDays(currentTime, -1);
          size = Math.round(dpdClient.getFileMetric(user, projectName, currentTime, totalLines)
              .getTotal());
        }
        summary.setTotalLines(size);
      }
      long totalEffort = 0;
      XMLGregorianCalendar currentTime = dataStartTime;
      if (Tstamp.greaterThan(currentTime, date)) {
        totalEffort = 0; // project hasn't started yet.
        summary.setTotalEffort(0);
      }
      else {
        long dailyEffort;
        Day activeDay;
        while (Tstamp.greaterThan(date, currentTime)) {
          dailyEffort = dpdClient.getDevTime(user, projectName, currentTime).getTotalDevTime()
              .longValue();
          totalEffort += dailyEffort;
          if (dailyEffort > 0) {
            activeDay = Day.getInstance(currentTime.getYear(), currentTime.getMonth() - 1,
                currentTime.getDay());
            summary.addActiveDay(new DailyEffort(activeDay, dailyEffort));
          }
          // increment the date
          currentTime = Tstamp.incrementDays(currentTime, 1);
        }
        // get last day's data
        dailyEffort = dpdClient.getDevTime(user, projectName, currentTime).getTotalDevTime()
            .longValue();
        totalEffort += dailyEffort;
        if (dailyEffort > 0) {
          activeDay = Day.getInstance(currentTime.getYear(), currentTime.getMonth() - 1,
              currentTime.getDay());
          summary.addActiveDay(new DailyEffort(activeDay, dailyEffort));
        }

        summary.setTotalEffort(totalEffort);
      }
    }
    catch (DailyProjectDataClientException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    catch (SensorBaseClientException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }

  }

  /**
   * Returns the timestamp of the first sensordata for the given project.
   * 
   * @param user
   *            a String, the user's email.
   * @param projectName
   *            a String, the project name.
   * @return The XMLGregorianCalendar representing the time of the first sensor
   *         data entry.
   * @throws SensorBaseClientException
   *             if there is a problem with the sensorbase client.
   */
  private XMLGregorianCalendar startDate(String user, String projectName)
      throws SensorBaseClientException {
    // find first day of data
    SensorDataIndex index = sbClient.getProjectSensorData(user, projectName);
    List<SensorDataRef> data = index.getSensorDataRef();
    SensorData firstEntry = sbClient.getSensorData(data.get(0));
    XMLGregorianCalendar dataTime = firstEntry.getTimestamp();
    // now convert to uniform starting time
    Day day = Day.getInstance(dataTime.getYear(), dataTime.getMonth() - 1, dataTime.getDay());
    return Tstamp.makeTimestamp(day);
  }

  /**
   * Commandline interface.
   * 
   * @param args
   *            a String[], the command line arguments, ignored.
   * @throws ProjectException
   *             if the ProjectExplorer can't be created.
   * @throws SensorBaseClientException
   *             if can't add project.
   * @throws DailyProjectDataClientException
   *             if there is a problem with the DPD.
   */
  public static void main(String args[]) throws ProjectException, SensorBaseClientException,
      DailyProjectDataClientException {
    ProjectExplorer explorer = new ProjectExplorer();
    Day today = Day.getInstance();
    Day yesterday = today.inc(-1);
    try {
      ProjectIndex index = explorer.sbClient.getProjectIndex(explorer.properties.getEmail());
      for (ProjectRef ref : index.getProjectRef()) {
        Project proj = explorer.sbClient.getProject(ref);
        System.out.println(explorer.toDetailedDescription(proj));
        if (proj.getName().equals("DPDSize")) {
          List<String> uris = new ArrayList<String>();
          uris.add("*/hackystat-dailyprojectdata/*");
          // explorer.createProject(proj.getName(), proj.getDescription(),
          // uris, proj.getStartTime(), proj.getEndTime());
        }
        try {
          String devTimeStr = explorer.dpdClient.getDevTime(explorer.properties.getEmail(),
              proj.getName(), Tstamp.makeTimestamp(today)).getTotalDevTime().toString();
          double fileSize = explorer.dpdClient.getFileMetric(explorer.properties.getEmail(),
              proj.getName(), Tstamp.makeTimestamp(today), totalLines).getTotal();
          System.out.println(explorer.properties.getEmail() + " has spent " + devTimeStr
              + " minute(s)  working on " + proj.getName() + " today which is " + fileSize
              + " lines.");
          devTimeStr = explorer.dpdClient.getDevTime(explorer.properties.getEmail(),
              proj.getName(), Tstamp.makeTimestamp(yesterday)).getTotalDevTime().toString();
          fileSize = explorer.dpdClient.getFileMetric(explorer.properties.getEmail(),
              proj.getName(), Tstamp.makeTimestamp(yesterday), totalLines).getTotal();
          System.out.println(explorer.properties.getEmail() + " has spent " + devTimeStr
              + " minute(s)  working on " + proj.getName() + " yesterday  which is " + fileSize
              + " lines.");
        }
        catch (DailyProjectDataClientException e) {
          e.printStackTrace();
        }
      }
      // List<Project> allProjects = explorer.getAllProjects();
      // for (int i = 0; i < allProjects.size(); i++) {
      // System.out.println(explorer.toDetailedDescription(allProjects.get(i)));
      // }
    }
    catch (SensorBaseClientException e) {
      e.printStackTrace();
    }
    // Day start = Day.getInstance(2007, 9, 1); // Oct 1, 20007
    // Day end = Day.getInstance(2008, 9, 1); // Oct 1, 2008

    ProjectSummary summary = explorer.getTodaysSummary(explorer.properties.getEmail(), "Default");
    System.out.println(summary);
    Vector<DailyEffort> activeDays = summary.getActiveDayVector();
    System.out.println(activeDays);

    // ArrayList<ProjectSummary> data = new ArrayList<ProjectSummary>();
    // data.add(summary);

    // ProjectSummary early =
    // explorer.getProjectSummary(explorer.properties.getEmail(), "Default",
    // Tstamp.makeTimestamp(activeDays.elementAt(2).getDay()));
    // data.add(early);
    // System.out.println(early);
    // early = explorer.getProjectSummary(explorer.properties.getEmail(),
    // "Default",
    // Tstamp.makeTimestamp(activeDays.elementAt(activeDays.size() /
    // 2).getDay()));
    // System.out.println(early);
    // data.add(early);
    // ProjectAnalysisHelper analysis = new ProjectAnalysisHelper(data);
    // analysis.recalculateValues();

    // System.out.println("Max LOC per Day = " + analysis.getMaxLocPerDay());
    // System.out.println("Average LOC per Day = " +
    // analysis.getMeanLocPerDay());
    // System.out.println("Min LOC per Day = " + analysis.getMinLocPerDay());
    // System.out.println("Std Dev LOC per Day = " +
    // analysis.getStdDevLocPerDay());

    // System.out.println("Max LOC per Hour = " + analysis.getMaxLocPerHour());
    // System.out.println("Average LOC per Hour = " +
    // analysis.getMeanLocPerHour());
    // System.out.println("Min LOC per Hour = " + analysis.getMinLocPerHour());
    // System.out.println("Std Dev LOC per Hour = " +
    // analysis.getStdDevLocPerHour());

    // System.out.println("Max minutes per Day = " +
    // analysis.getMaxMinutesPerDay());
    // System.out.println("Average minutes per Day = " +
    // analysis.getMeanMinutesPerDay());
    // System.out.println("Min minutes per Day = " +
    // analysis.getMinMinutesPerDay());
    // System.out.println("Std Dev minutes per Day = " +
    // analysis.getStdDevMinutesPerDay());

  }
}
